﻿using System;
using Microsoft.Xna.Framework;
using Tomahawk.Runtime.Rendering.Particles;
using System.Xml.Serialization;
using Tomahawk.Serialization;
using Tomahawk.Runtime.Scene;
using System.Collections;
using Tomahawk.Serialization.SerialBoost;
using Tomahawk.Runtime.Resources;

namespace Tomahawk.Runtime.Logic
{
    public class XParticleEmitterComponent : XRenderableComponent
    {
        #region ParticleEmitter

        public class ParticleEmitter
        {
            #region Serializable Properties

            [XmlAttribute("pool")]
            public ResourceManager.ResourcePool ResourcePool = ResourceManager.ResourcePool.GameState;

            [XmlElement("Name")]
            public string name = "";
            
            [XmlElement("Settings")]
            public string settings = "";
            
            [XmlElement("ParticlesPerSecond")]
            public float particlesPerSecond = 0.0f;
            
            [XmlElement("Source")]
            public ParticleSource source = null;
            
            [XmlElement("Enabled")]
            public bool enabled = false;

            protected string renderGroups = "Color";

            [XmlElement("RenderGroups")]
            public string RenderGroups
            {
                get { return renderGroups; }
                set
                {
                    renderGroups = value;
                    if (sceneNode != null)
                    {
                        sceneNode.RenderGroupNames = this.RenderGroups;
                    }
                }
            }
            #endregion

            #region Properties
            private SceneNode sceneNode = null;
            private ParticleSystem particleSystem = null;
            private float timeBetweenParticles = 0.0f;
            private Vector3 previousPosition = Vector3.Zero;
            private float timeLeftOver = 0.0f;

            public bool IsReady { get { return particleSystem != null && particleSystem.IsReady; } }
            public Vector3 PreviousPosition { get { return previousPosition; } set { previousPosition = value; } }
            public ParticleSystem ParticleSystem { get { return particleSystem; } }
            #endregion

            #region Initialization and Update
            public void Initialize(SceneNode node)
            {
                if (node != null)
                {
                    this.particleSystem = new ParticleSystem(settings, this.ResourcePool);
                    this.timeBetweenParticles = particlesPerSecond > 0.0f ? 1.0f / particlesPerSecond : float.MaxValue;
                    this.previousPosition = Vector3.Zero;
                    this.sceneNode = node.CreateChildSceneNode();
                    this.sceneNode.Attach(this.particleSystem);
                    this.sceneNode.RenderGroupNames = this.RenderGroups;
                }                
            }

            public void Update(ref Matrix worldMatrix, float elapsedTime)
            {
                Vector3 newPosition = worldMatrix.Translation;

                if (source != null && elapsedTime > 0)
                {
                    Vector3 velocity = (newPosition - previousPosition) / elapsedTime;

                    float timeToSpend = timeLeftOver + elapsedTime;

                    float currentTime = -timeLeftOver;
                    while (timeToSpend > timeBetweenParticles)
                    {
                        currentTime += timeBetweenParticles;
                        timeToSpend -= timeBetweenParticles;

                        if (enabled)
                        {
                            float mu = currentTime / elapsedTime;
                            worldMatrix.Translation = Vector3.Lerp(previousPosition, newPosition, mu);

                            particleSystem.AddParticle(
                                source.GenerateParticlePosition(ref worldMatrix),
                                source.GenerateParticleVelocity(ref worldMatrix) + velocity);
                        }
                    }
                    timeLeftOver = timeToSpend;

                    particleSystem.Update(elapsedTime);
                }

                previousPosition = newPosition;
            }
            #endregion
        }
        #endregion

        #region Serializable Properties

        [XmlElement("Emitters")]
        public SerializableList<ParticleEmitter> emitters = new SerializableList<ParticleEmitter>();
        
        #endregion

        protected float timeout = -1.0f;

        #region Properties

        [XmlIgnore()]
        public bool Enabled
        {
            get
            {
                bool result = true;
                for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx)
                {
                    result &= emitters[emitterIdx].enabled;
                }
                return result;
            }

            set 
            {
                for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx)
                {
                    emitters[emitterIdx].enabled = value;
                }
            }
        }

        #endregion

        public XParticleEmitterComponent()
        {
            UpdateRequired = true;
        }

        public void SetTimeout(float elapsedTime)
        {
            timeout = elapsedTime;
        }

        protected override bool checkResourcesReady()
        {
            bool particlesReady = true;
            for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx)
            {
                particlesReady &= emitters[emitterIdx].IsReady;
            }

            return base.checkResourcesReady() && particlesReady;
        }

        protected override void OnInitialize()
        {
            base.OnInitialize();

            for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx )
            {
                emitters[emitterIdx].Initialize(this.SceneNode);
            }
        }

        protected override void OnPostBeginPlay()
        {
            base.OnPostBeginPlay();

            for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx)
            {
                emitters[emitterIdx].PreviousPosition = SceneNode.Position;
            }
        }

        protected override void OnUpdate(float elapsedTime)
        {
            base.OnUpdate(elapsedTime);

            if (timeout > 0.0f)
            {
                timeout -= elapsedTime;
                if (timeout <= 0.0f)
                {
                    Enabled = false;
                }
            }

            Matrix worldMatrix = SceneNode.WorldMatrix;
            
            for (int emitterIdx = 0; emitterIdx < emitters.Count; ++emitterIdx)
            {
                emitters[emitterIdx].Update(ref worldMatrix, elapsedTime);
            }
        }
    }
}

